I have some JavaScript code consisting of several functions that may or may not call each other. Here is an example:
function a(x) { return 2*x; }
function b(x, y) { return a(y) + x; }
function c() { return b(1, 2); }
I have a special function that takes a function and its arguments and returns the computed value (among other things, see explanation below):
function pre(f, ...args) { return f(...args); }
Is it possible to "prefix" all function calls by this function WITHOUT needing to modify/reassign the functions?
I.e. the call pre(c); should behave as if the functions were defined like this:
function a(x) { return 2*x; }
function b(x, y) { return pre(a, y) + x; }
function c() { return pre(b, 1, 2); }
c();), that is alright.Use case:
I have a (Java) program in which an end-user can define JavaScript functions. This program reads some external data and calls these JavaScript functions on it. Finally, some result from these function calls will be given back to the end-user.
The user-defined functions are side-effect free (the program makes sure of that); they are pure calculation functions. Because of that, it suffices to call each function mostly once - they will always give the same output for identical input. So I wanted to implement a caching mechanism inside pre like e.g. this:
var cache = new Map();
function pre(f, ...args) {
var array = new Array(args.length + 1);
array[0] = f.name;
for (var i = 0; i < args.length; i++) {
array[i+1] = args[i];
}
var stringified = JSON.stringify(array);
if (cache.has(stringified)) {
return cache.get(stringified);
}
else {
var calc = Object.freeze(f(...args));
cache.set(stringified, calc);
return calc;
}
}
I currently implemented it so that I just modify all user-defined functions (which are given as strings, so I manipulate strings) to contain the pre call. But I'm interested in a more elegant solution - a solution that does not require code modification - and if there is, I'm sure the knowledge of that solution can help in many situations.
If I'm understanding you correctly, you want to be able to define functions like this:
function a(x) { return 2*x; }
function b(x, y) { return a(y) + x; }
function c() { return b(1, 2); }
and then after defining them (after the functions and the a, b, and c bindings for them exist), intercept all of the calls to a, b, or c (including the ones inside b and c) without reassigning the a, b, or c identifiers (more formally, bindings).
You can't do that. It would require reaching into b (for instance) and changing which a binding b uses to find and call the function assigned to a. You can change the value of the a binding that b uses (e.g., which function a refers to) by reassigning it, but not which a binding b uses.
Just for the sake of any lurkers who want to know how to do it with reassignment, here's how, but the OP has specifically said they don't want to do this:
var cache = new Map();
function pre(f, ...args) {
var array = new Array(args.length + 1);
array[0] = f.name;
for (var i = 0; i < args.length; i++) {
array[i+1] = args[i];
}
var stringified = JSON.stringify(array);
if (cache.has(stringified)) {
return cache.get(stringified);
}
else {
var calc = Object.freeze(f(...args));
cache.set(stringified, calc);
return calc;
}
}
// Doesn't handle `this` because the OP didn't need to and the
// `pre` function would have to be modified to handle it
function wrap(original) {
return (...args) => pre(original, ...args);
}
function a(x) {
console.log(`a called with`, x);
return 2 * x;
}
function b(x, y) {
console.log(`b called with`, x, y);
return a(y) + x;
}
function c() {
console.log(`c called`);
return b(1, 2);
}
a = wrap(a);
b = wrap(b);
c = wrap(c);
console.log(`First call:`);
console.log(c());
console.log(`Second call:`);
console.log(c());
In a comment you've said:
More precisely, I want to know if there is a level between "what b(1, 2) does" (the function itself) and the ... thing ... (the interpreter?) that says "now call that function". And if there is a level in-between, I want to know if that can be modified.
The only level in-between in a standard JavaScript environment is the lexical environment object that holds the a, b, and c bindings, which is what we'd be changing by reassigning them. There's nothing else we can do to intercept/adjust those function calls.